{
Traviesadora beta 0.2.0.13.33 (30.11.2008)
LastMod: 20.03.2009 by Prof1983

Copyright 2008, travieorama@googlemail.com

This file is part of the taviesadora project

taviesadora is free software; you can redistribute it and/or modify
it under the terms of the GNU General Public License as published by
the Free Software Foundation; either version 2 of the License, or
(at your option) any later version.

taviesadora is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
GNU General Public License for more details.
}
unit RosterViewer;

interface

uses
  Classes, SysUtils, ComCtrls, Forms, Graphics, ChatDistributer, CommCtrl, ImgList, Windows,
  Avatar, VirtualTrees, RosterDB, IdGlobal, RosterGlobals, TraviesadoraCore;

type
  TNodeData = record
    level: Integer;
    ImageIndex: Integer;
    SelectedIndex: Integer;
    StateIndex: Integer;
    text: string;
    PContactInfo: PContactData;
  end;
  PNodeData = ^TNodeData;

type
  TRosterViewer = class(TObject)
  private
    FOnPresenceChanged: TNotifyEvent;
    FOnMyAvatarChanged: TNotifyEvent;
    trvRosterList: TVirtualDrawTree;
    Roster: TRosterDB;
    Owner: TForm;
    AvatarCache: TAvatarCache;
    FShowOnlyOnline: Boolean;
    ShowAvatars: Boolean;
    FTravCore: ITraviesadoraCoreA;
    procedure ClearNodes;
    function CreateNewNodeGroup(ContactState: TContactState; NetType: TNetType): PVirtualNode;
    procedure DelEmptyNodes;
    function GetNodeGroup(ContactState: TContactState;NetType:TNetType): PVirtualNode;
    function SearchNode(jid: string): PVirtualNode;
    function CreateNewNodeNetGroup(NetType: TNetType): PVirtualNode;
    function GetNodeNetGroup(NetType: TNetType): PVirtualNode;
    function GetNodeGroupByNode(ContactState: TContactState; Node: PVirtualNode): PVirtualNode;
    procedure PaintText(Sender: TBaseVirtualTree; const TargetCanvas: TCanvas;
      Node: PVirtualNode; Column: TColumnIndex; TextType: TVSTTextType);
    procedure trvRosterListGetImageIndex(Sender: TBaseVirtualTree; Node: PVirtualNode;
      Kind: TVTImageKind; Column: TColumnIndex; var Ghosted: Boolean; var ImageIndex: Integer);
    procedure trvRosterListGetHint(Sender: TBaseVirtualTree; Node: PVirtualNode;
      Column: TColumnIndex; var LineBreakStyle: TVTTooltipLineBreakStyle; var HintText: WideString);
    procedure trvRosterListAfterCellPaint(Sender: TBaseVirtualTree; TargetCanvas: TCanvas;
      Node: PVirtualNode; Column: TColumnIndex; CellRect: TRect);
    procedure trvRosterListGetNodeWidth(Sender: TBaseVirtualTree; HintCanvas: TCanvas;
      Node: PVirtualNode; Column: TColumnIndex; var NodeWidth: Integer);
    procedure trvRosterListDrawHint(Sender: TBaseVirtualTree; HintCanvas: TCanvas;
      Node: PVirtualNode; R: TRect; Column: TColumnIndex);
    procedure trvRosterListGetHintSize(Sender: TBaseVirtualTree;
      Node: PVirtualNode; Column: TColumnIndex; var R: TRect);
    procedure PaintAvatar1(TargetCanvas: TCanvas; CellRect: TRect; Width, Height: Integer; Avatar: TAvatar);
  public
    Shadow: TGraphic;
    constructor Create(TravCore: ITraviesadoraCoreA; TreeView: TVirtualDrawTree; {Roster: TRosterDB;} Owner: TForm;
        ShowOnlyOnline: Boolean; ShowAvatars: Boolean; AvatarCache: TAvatarCache);
    destructor Destroy; override;
    procedure PresenceChanged;
    procedure MyAvatarChanged;
    procedure PresenceChange(ContactInfo: PContactData);
    procedure AvailChange(ContactInfo: TContactData);
    procedure RosterChange;
    procedure MakeRosterList;
    procedure Close;
    property OnPresenceChanged: TNotifyEvent read FOnPresenceChanged write FOnPresenceChanged;
    property OnMyAvatarChanged: TNotifyEvent read FOnMyAvatarChanged write FOnMyAvatarChanged;
  end;

implementation

uses
  Entity, EntityCache;

{ TRosterViewer }

constructor TRosterViewer.Create(TravCore: ITraviesadoraCoreA; Treeview: TVirtualDrawTree; {Roster: TRosterDB;} Owner: TForm;
  ShowOnlyOnline: Boolean; ShowAvatars: Boolean; AvatarCache: TAvatarCache);
begin
  inherited Create;
  FTravCore := TravCore;
  trvRosterList := TreeView;
  self.FShowOnlyOnline := ShowOnlyOnline;
  self.AvatarCache := AvatarCache;
  self.Owner := Owner;
  self.Roster := FTravCore.RosterDB;
  self.ShowAvatars := ShowAvatars;
  trvRosterList.OnAfterCellPaint := trvRosterListAfterCellPaint;
  trvRosterList.OnGetNodeWidth := trvRosterListGetNodeWidth;
  trvRosterList.OnDrawHint := trvRosterListDrawHint;
  trvRosterList.OnGetHintSize := trvRosterListGetHintSize;
  trvRosterList.OnGetImageIndex := trvRosterListGetImageIndex;
  Roster.RegisterCallBackRosterView(self);
  MakeRosterList;
end;

destructor TRosterViewer.destroy;
begin
  Roster.UnRegisterCallBackRosterView(self);
  ClearNodes;
  inherited destroy;
end;

procedure TRosterViewer.AvailChange(ContactInfo: TContactData);
begin
  PresenceChange(@ContactInfo);
end;

procedure TRosterViewer.trvRosterListDrawHint(Sender: TBaseVirtualTree;
  HintCanvas: TCanvas; Node: PVirtualNode; R: TRect; Column: TColumnIndex);
var
  ContactData: PContactData;
  NodeData: PNodeData;
  up: TUserProfile;
  s: string;
  e: TJabberEntity;
  t: Integer;
  i: Integer;
begin
  NodeData := sender.GetNodeData(Node);
  HintCanvas.Pen.Color := HintCanvas.Brush.Color;
  HintCanvas.Rectangle(r);
  if not(Assigned(NodeData)) then Exit;
  ContactData := NodeData.PContactInfo;
  if not(Assigned(ContactData)) then Exit;
  HintCanvas.Font.Style := [fsbold];
  t := r.Top+10;
  if ContactData.NetType = ntJabber then
  begin
    HintCanvas.TextOut(r.Left+10,t,ContactData.UserId);
    t := t+HintCanvas.TextHeight(ContactData.UserId);
    HintCanvas.Font.Style := [];
    up := AvatarCache.Find(contactData.UserId);
    if Assigned(up) then
    begin;
      HintCanvas.TextOut(r.Left+10,t,up.GetGender+' '+up.GetBirthday);
      t := t+HintCanvas.TextHeight(up.GetGender+' '+up.GetBirthday);
      HintCanvas.TextOut(r.Left+10,t,up.GetURL);
      t := t+HintCanvas.TextHeight(up.GetURL);
      HintCanvas.TextOut(r.Left+10,t,up.GetFavToy);
      t := t+HintCanvas.TextHeight(up.GetFavToy);
      r.Top := r.Top+10;
      PaintAvatar1(HintCanvas, R, 150, 110, up.GetAvatar);
    end;
    e := jEntityCache.getByJidwoResource(ContactData.UserId);
    if Assigned(e) then
    begin
      if Assigned(e.Version) then
      begin
        s := e.version.AppName;
        HintCanvas.TextOut(r.Left+10,t,s);
        t := t+HintCanvas.TextHeight(s);
        s := e.version.AppVersion;
        HintCanvas.TextOut(r.Left+10,t,s);
        t := t+HintCanvas.TextHeight(s);
        if (e.Version.AppOs <> '') then
        begin
          s := e.version.AppOs;
          HintCanvas.TextOut(r.Left+10,t,s);
          t := t+HintCanvas.TextHeight(s);
        end;
      end;
    end;
  end else if (ContactData.NetType = ntNobraNet) then
  begin
    HintCanvas.TextOut(r.Left+10,t,ContactData.UserId);
    t := t+HintCanvas.TextHeight(ContactData.UserId);
    HintCanvas.Font.Style := [];
    s := ContactData.nick;
    if (Pos('/',s) > 0) then
    begin
      Delete(s,1,pos('/',s));
      Delete(s,pos('-',s),length(s));
      s := UpCaseFirst(s);
      HintCanvas.TextOut(r.Left+10,t,s);
      t := t+HintCanvas.TextHeight(s);
      s := ContactData.nick;
      if (Pos('-',s) > 1) then
      begin
        Delete(s,1,pos('-',s));
        for i := 0 to length(s) do
          if s[i] = '_' then s[i] := ' ';
        HintCanvas.TextOut(r.Left+10,t,s);
        t := t+HintCanvas.TextHeight(s);
      end;
    end
    else
    begin
      HintCanvas.TextOut(r.Left+10,t,'NobraChat');
      t := t+HintCanvas.TextHeight(s);
    end;
  end;
end;

procedure TRosterViewer.trvRosterListGetHintSize(Sender: TBaseVirtualTree;
  Node: PVirtualNode; Column: TColumnIndex; var R: TRect);
var
  ContactData: PContactData;
  NodeData: PNodeData;
  //Avatar:TAvatar;
  e: TJabberEntity;
  up: TUserProfile;
  w,wl,h,i:integer;
begin
  NodeData := sender.GetNodeData(Node);
  if NodeData = nil then exit;
  ContactData := NodeData.PContactInfo;
  if ContactData = nil then exit;
  trvRosterList.Canvas.font.Style := [fsbold];
  wl := trvRosterList.Canvas.TextWidth(ContactData.UserId);
  h := trvRosterList.Canvas.TextHeight(ContactData.UserId)+20;
  trvRosterList.Canvas.font.Style := [];
  if ContactData.NetType = ntJabber then begin
    up := AvatarCache.Find(contactData.UserId);
    if up <> nil then begin
      if up.GetGender <> '' then
        h := h+trvRosterList.Canvas.TextHeight(up.GetGender);
      if wl < trvRosterList.Canvas.TextWidth(up.GetGender) then
        wl := trvRosterList.Canvas.TextWidth(up.GetGender);
      if up.GetURL <> '' then
        h := h+trvRosterList.Canvas.TextHeight(up.GetURL);
      if wl < trvRosterList.Canvas.TextWidth(up.GetURL) then
        wl := trvRosterList.Canvas.TextWidth(up.GetURL);
      if up.GetBirthday <> '' then
        h := h+trvRosterList.Canvas.TextHeight(up.GetBirthday);
      if wl < trvRosterList.Canvas.TextWidth(up.GetBirthday) then
        wl := trvRosterList.Canvas.TextWidth(up.GetBirthday);
      if up.GetFavToy <> '' then
        h := h+trvRosterList.Canvas.TextHeight(up.GetFavToy);
      if wl < trvRosterList.Canvas.TextWidth(up.GetFavToy) then
        wl := trvRosterList.Canvas.TextWidth(up.GetFavToy);
      if up.GetAvatar <> nil then begin
        w := 170;
        if h < 120 then
          h := 120;
      end else
        w := 0;
    end;
    e := jEntityCache.getByJidwoResource(ContactData.UserId);
    if e <> nil then begin
      if e.version <> nil then begin
        if wl < trvRosterList.Canvas.TextWidth(e.version.AppName) then
          wl := trvRosterList.Canvas.TextWidth(e.version.AppName);
        if wl < trvRosterList.Canvas.TextWidth(e.version.AppVersion) then
          wl := trvRosterList.Canvas.TextWidth(e.version.AppVersion);
        if wl < trvRosterList.Canvas.TextWidth(e.version.AppOs) then
          wl := trvRosterList.Canvas.TextWidth(e.version.AppOs);
        h := h+2*trvRosterList.Canvas.TextHeight(e.version.AppName+' '+e.version.AppVersion);
        if e.Version.AppOs <> '' then
          h := h+2*trvRosterList.Canvas.TextHeight(e.version.AppOs);
      end;
    end;
    w := wl + w+20;
  end
  else if ContactData.NetType = ntNobraNet then begin
    if (pos('/',contactData.nick)>0)and (pos('-',contactData.nick)>0) then
      i := 2
    else
      i := 1;
    h := h+trvRosterList.Canvas.TextHeight(contactData.nick)*i;
  end;
  h := h + 3;
  r.Bottom := r.Top+h;
  r.Right := r.Left+w;
end;

procedure TRosterViewer.trvRosterListAfterCellPaint(Sender: TBaseVirtualTree;
  TargetCanvas: TCanvas; Node: PVirtualNode; Column: TColumnIndex;
  CellRect: TRect);
var
  ContactData: PContactData;
  NodeData: PNodeData;
  Avatar: TAvatar;
  up: TUserProfile;
  s: string;
  r: TRect;
begin
  NodeData := sender.GetNodeData(Node);
  if NodeData = nil then exit;
  if vsSelected in Node.States then
  begin
    TargetCanvas.Font.Color := clwhite;
    //TargetCanvas.Brush.Color := clHighlight;
  end;
  if NodeData.Level = 0 then
    TargetCanvas.Font.Style := [fsbold];
  if NodeData.Level = 1 then
    TargetCanvas.Font.Style := [];
  TargetCanvas.Font.Color := clblack;
  if (NodeData.Level = 2) then
  begin
    TargetCanvas.Font.Style := [];
    ContactData := NodeData.PContactInfo;
    if Assigned(ContactData) then
    begin
      if vsSelected in Node.States then
      begin
        TargetCanvas.Font.Color := clwhite;
      end
      else
      begin
        if ContactData.status = csOffline then
        begin
          TargetCanvas.Font.Color := clGray;
        end else begin
          TargetCanvas.Font.Color := clblack;
        end;
      end;
      if ContactData.NetType = ntJabber then
      begin
        if AvatarCache = nil then Exit;
        if ShowAvatars = false then Exit;
        up := Avatarcache.find(ContactData.UserId);
        if Assigned(up) then
        begin
          Avatar := up.GetAvatar;
          if Assigned(Avatar) then
          begin
            trvRosterList.NodeHeight[Node] := 85;
            PaintAvatar1(TargetCanvas, CellRect, 100, 85, Avatar);
          end
          else
           trvRosterList.NodeHeight[Node] := trvRosterList.DefaultNodeHeight;
        end;
      end;
    end;
  end;
  r := Sender.GetDisplayRect(Node,Column,true);
  s := Nodedata.text;
  if NodeData.Level = 0 then
  begin
    if (LowerCase(nodedata.text) = 'jabber') then
      if FTravCore.Jabber.SSL_IsOn then
      begin
        s := s +' (tsl)';
      end;
  end;
  TargetCanvas.TextOut(r.Left+2,{CellRect.Top+}trvRosterList.NodeHeight[Node] div 2 - TargetCanvas.TextHeight(s) div 2,s);                                        //  }
end;

procedure TRosterViewer.trvRosterListGetNodeWidth(Sender: TBaseVirtualTree;
  HintCanvas: TCanvas; Node: PVirtualNode; Column: TColumnIndex;
  var NodeWidth: Integer);
var
  NodeData: PNodeData;
begin
  NodeData := sender.GetNodeData(Node);
  if NodeData = nil then exit;
  if NodeData.level = 0 then
    trvRosterList.Canvas.Font.Style := [fsbold]
  else
    trvRosterList.Canvas.Font.Style := [];
  NodeWidth := trvRosterList.Canvas.TextWidth(Nodedata.text)+5;
end;

procedure TRosterViewer.trvRosterListGetHint(Sender: TBaseVirtualTree; Node: PVirtualNode;
  Column: TColumnIndex; var LineBreakStyle: TVTTooltipLineBreakStyle; var HintText: WideString);
var
  UserProfile: TUserProfile;
  NodeData: PNodeData;
  ContactData: PContactData;
begin
  NodeData := trvRosterList.GetNodeData(Node);
  if NOde = nil then exit;
  ContactData := NodeData.PContactInfo;
  if ContactData = nil then exit;
  UserProfile := AvatarCache.Find(ContactData.UserId);
  if UserProfile = nil then exit;
  HintText := ContactData.UserId + #13+'Nick:'+UserProfile.GetNick+#13+UserProfile.GetGender;
end;

procedure TRosterViewer.PaintText(Sender: TBaseVirtualTree; const TargetCanvas: TCanvas;
  Node: PVirtualNode; Column: TColumnIndex; TextType: TVSTTextType);
var
  ContactData: PContactData;
  NodeData: PNodeData;
begin
  NodeData := sender.GetNodeData(Node);
  if not(Assigned(NodeData)) then Exit;
  if NodeData.Level = 0 then
  begin
    TargetCanvas.Font.Style := [fsbold];
  end else if NodeData.level = 2 then
  begin
    ContactData := NodeData.PContactInfo;
    if NOde.States = [vsSelected] then
    begin
      TargetCanvas.Font.Color := clwhite;
    end
    else
    begin
      if ContactData.status = csOffline then
      begin
        TargetCanvas.Font.Color := clGray;
      end
      else
      begin
        TargetCanvas.Font.Color := clblack;
      end;
    end;
  end;
end;

procedure TRosterViewer.trvRosterListGetImageIndex(Sender: TBaseVirtualTree;
  Node: PVirtualNode; Kind: TVTImageKind; Column: TColumnIndex;
  var Ghosted: Boolean; var ImageIndex: Integer);
var
  //ContactData: PContactData;
  NodeData: PNodeData;
begin
  NodeData := sender.GetNodeData(Node);
  if NodeData = nil then exit;
  if (kind = ikNormal) or (kind = ikSelected) then
  begin
    if NodeData.ImageIndex = 0 then
    begin
      NodeData.ImageIndex := 0;
    end;
    ImageIndex := NodeData.ImageIndex;
  end
  else
    ImageIndex := -1;
end;

procedure TRosterViewer.PresenceChange(ContactInfo: PContactData);
var
  Node: PVirtualNode;
  NodeRoot: PVirtualNode;
  NodeData: PNodeData;
begin
  if (ContactInfo = nil) or FShowOnlyOnline then
  begin
    MakeRosterList();
  end
  else
  begin
    Node := SearchNode(ContactInfo.UserId);
    if node = nil then exit;
    NodeData := trvRosterList.GetNodeData(node);
    NodeData.ImageIndex := ContactInfo.imgIndex;
    NodeData.SelectedIndex := NodeData.ImageIndex;
    NodeData.StateIndex := NodeData.ImageIndex;
    NodeRoot := GetNodeGroup(ContactInfo.status,ContactInfo.NetType);
    if NodeRoot = nil then
      NodeRoot := CreateNewNodeGroup(ContactInfo.status,ContactInfo.NetType);
    trvRosterList.MoveTo(Node,NodeRoot,amAddChildFirst,false);
    trvRosterList.Expanded[NodeRoot] := true;
    DelEmptyNodes;
  end;
end;

procedure TRosterViewer.RosterChange;
begin
  MakeRosterList;
end;

procedure TRosterViewer.DelEmptyNodes();
var
  NodeChild1: PVirtualNode;
  NodeChild2: PVirtualNode;
  Node: PVirtualNode;
begin
  Node := trvRosterList.GetFirst;
  while Assigned(Node) do
  begin
    NodeChild1 := trvRosterList.GetFirstChild(Node);
    while Assigned(NodeChild1) do
    begin
      if trvRosterList.GetFirstChild(NodeChild1)=nil  then
      begin
        NodeChild2 := NodeChild1;
        NodeChild1 := trvRosterList.GetNextSibling(NodeChild1);
        trvRosterList.DeleteNode(NodeChild2);
      end
      else
        NodeChild1 := trvRosterList.GetNextSibling(NodeChild1);
    end;
    if trvRosterList.GetFirstChild(Node)=nil then
    begin
      NodeChild1 := Node;
      Node := trvRosterList.GetNextSibling(Node);
      trvRosterList.DeleteNode(NodeChild1);
    end
    else
      if Assigned(Node) then
        Node := trvRosterList.GetNextSibling(Node);
  end;
  Node := trvRosterList.GetFirst;
  if not(Assigned(Node)) then
  begin
    CreateNewNodeNetGroup(ntNone);
  end;
end;

procedure TRosterViewer.ClearNodes();
begin
  trvRosterList.Clear();
end;

function TRosterViewer.SearchNode(jid: string): PVirtualNode;
var
  ContactInfo: PContactData;
  NodeChild1: PVirtualNode;
  NodeChild2: PVirtualNode;
  Node: PVirtualNode;
  NodeData: PnodeData;
begin
  result := nil;
  Node := trvRosterList.GetFirst;
  while Assigned(Node) do
  begin
    NodeChild1 := trvRosterList.GetFirstChild(Node);
    while Assigned(NodeChild1) do
    begin
      NodeChild2:= trvRosterList.GetFirstChild(NodeChild1);
      while Assigned(NodeChild2) do
      begin
        NodeData := trvRosterList.GetNodeData(NodeChild2);
        if Assigned(NodeData) then
        begin
          ContactInfo := NodeData.PContactInfo;
          if (ContactInfo.UserId = jid) then
          begin
            Result := NodeChild2;
            Break;
          end;
        end;
        NodeChild2 := trvRosterList.GetNextSibling(NodeChild2);
      end;
      NodeChild1 := trvRosterList.GetNextSibling(NodeChild1);
    end;
    if Assigned(Result) then
      Break;
    Node := trvRosterList.GetNextSibling(Node);
  end;
end;

function TRosterViewer.GetNodeNetGroup(NetType:TNetType):PVirtualNode;
var
  t: string;
  node: PVirtualNode;
  NodeData: PNodeData;
begin
  result := nil;
  t := '';
  if (NetType = ntJabber) then
  begin
    t := 'Jabber';
  end else if (NetType = ntNobraNet) then
  begin
    t := 'NobraNet';
  end;
  node := trvRosterList.GetFirst;
  while Assigned(node) do
  begin
    NodeData := trvRosterList.GetNodeData(node);
    if LowerCase(NodeData.Text) = LowerCase(t) then
    begin
      result := node;
      break;
    end;
    node := trvRosterList.GetNextSibling(node);
  end;
end;

function TRosterViewer.GetNodeGroup(ContactState:TContactState;NetType:TNetType):PVirtualNode;
begin
  result := GetNodeNetGroup(NetType);
  result := GetNodeGroupByNode(ContactState,result);
end;

function TRosterViewer.GetNodeGroupByNode(ContactState:TContactState;Node:PVirtualNode):PVirtualNode;
var
  t: string;
  found: Boolean;
  NodeData: PNodeData;
begin
  t := '';
  result := Node;
  if ContactState = csOffline then
  begin
    t := 'Offline';
  end
  else if ContactState = csPending then
  begin
    t := 'Pending';
  end
  else
  begin
    t := 'Online';
  end;
  if Assigned(Result) then
  begin
    found := False;
    result := trvRosterList.GetFirstChild(result);
    while Assigned(Result) do
    begin
      NodeData := trvRosterList.GetNodeData(result);
      if Assigned(NodeData) then
        if LowerCase(NodeData.Text) = LowerCase(t) then
        begin
          found := true;
          break;
        end;
      result := trvRosterList.GetNextSibling(result);
    end;
    if not found then
      result := nil;
  end;
end;

function TRosterViewer.CreateNewNodeNetGroup(NetType: TNetType): PVirtualNode;
var
  NodeData: PNodeData;
  t: string;
begin
  Result := GetNodeNetGroup(NetType);
  if not(Assigned(Result)) then
  begin
    if NetType = ntJabber then
    begin
      t := 'Jabber';
      result := trvRosterList.AddChild(nil);
    end
    else if NetType = ntNobraNet then
    begin
      t := 'NobraNet';
      result := trvRosterList.AddChild(nil);
    end
    else if NetType = ntNone then
    begin
      t := 'Contacts (empty):';
      result := trvRosterList.AddChild(nil);
    end;
    if Assigned(Result) then
    begin
      NodeData := trvRosterList.GetNodeData(result);
      NodeData.text := t;
      NodeData.ImageIndex := -1;
      NodeData.SelectedIndex := -1;
      NodeData.StateIndex := -1;
      NodeData.level := 0;
      NodeData.PContactInfo := nil;
    end;
  end;
end;

function TRosterViewer.CreateNewNodeGroup(ContactState: TContactState; NetType: TNetType): PVirtualNode;
var
  Root: PVirtualNode;
  t: string;
  NodeData: PNodeData;
begin
  Root := CreateNewNodeNetGroup(NetType);
  if ContactState = csOffline then
  begin
    t := 'Offline';
  end
  else if ContactState = csPending then
  begin
    t := 'Pending';
  end
  else
  begin
    t := 'Online';
  end;
  result := GetNodeGroupByNode(ContactState,root);
  if not(Assigned(Result)) then
  begin
    Result := trvRosterList.AddChild(Root);
    NodeData := trvRosterList.GetNodeData(result);
    NodeData.ImageIndex := -1;
    NodeData.SelectedIndex := -1;
    NodeData.StateIndex := -1;
    NodeData.level := 1;
    NodeData.text := t;
    NodeData.PContactInfo := nil;
  end;
end;

procedure TRosterViewer.MakeRosterList;
var
  i: Integer;
  Root: PVirtualNode;
  Node: PVirtualNode;
  s: string;
  ContactData: PContactData;
  NodeData: PNodeData;
begin
  if not(Assigned(Roster)) then Exit;
  trvRosterList.NodeDataSize := SizeOf(TNodeData);
  ClearNodes;
  if not(FShowOnlyOnline) then
    CreateNewNodeGroup(csPending, ntJabber);
  CreateNewNodeGroup(csAvailable, ntJabber);
  CreateNewNodeGroup(csAvailable, ntNobraNet);
  if not(FShowOnlyOnline) then
    CreateNewNodeGroup(csOffline, ntJabber);
  for i := 0 to Roster.Count-1 do
  begin
    ContactData := Roster.GetPresenceByID(i);
    if (FShowOnlyOnline) and ((ContactData.status = csOffline) or (ContactData.status = csPending)) then continue;
    Root := GetNodeGroup(ContactData.status,ContactData.NetType);
    s := ContactData.nick;
    delete(s,pos('/traviesadora',s),length(s));
    if ContactData.status = csBlocked then
      s := s +'(Blocked)';
    if ContactData.whiteListed then
      s := s +'(Always Accept)';
    if Root = nil then
      Root := CreateNewNodeGroup(ContactData.status,ContactData.NetType);
    Node := trvRosterList.AddChild(Root);
    NodeData := trvRosterList.GetNodeData(Node);
    NodeData.text := s;
    NodeData.level := 2;
    NodeData.PContactInfo := ContactData;
    NodeData.ImageIndex := ContactData.imgIndex;
    NodeData.SelectedIndex := NodeData.ImageIndex;
    NodeData.StateIndex := NodeData.ImageIndex;
  end;
  // Prof1983
  //DelEmptyNodes;
  trvRosterList.FullExpand();
end;

procedure TRosterViewer.PresenceChanged;
begin
  RosterChange;
  if Assigned(FOnPresenceChanged) then
    FOnPresenceChanged(Self);
end;

procedure TRosterViewer.MyAvatarChanged();
begin
  if Assigned(FOnMyAvatarChanged) then
    FOnMyAvatarChanged(Self);
end;

procedure TRosterViewer.Close();
begin
  if Assigned(Owner) then
    Owner.Close;
end;

procedure TRosterViewer.PaintAvatar1(TargetCanvas: TCanvas; CellRect: TRect; Width, Height: Integer; Avatar: TAvatar);
var
  r: TRect;
  r2: TRect;
  w,h,l,t,aw,ah,i: Integer;
begin
  if Assigned(Avatar) then
  begin
    if Assigned(TargetCanvas) then
    begin
      aw := avatar.Width;
      ah := Avatar.Height;
      if Assigned(Shadow) then
        Height := Height-13;
      for i := 0 to 1 do
      begin
        if (aw > Width) or (ah > Height) then
        begin
          if ((aw >= ah) and (aw>width) and (ah>height)) or ((aw>width) and (ah<=height)) then
          begin
            w := width;
            h := round(ah*w/aw);
          end
          else
          begin
            h := Height;
            w := Round(aw*h/ah);
          end;
        end
        else
        begin
          if (aw >= ah) then
          begin
            w := aw;
            h := ah;
          end
          else
          begin
            h := ah;
            w := aw;
          end;
        end;
        aw := w;
        ah := h;
      end;
           // trvRosterList.NodeHeight[Node]:=85;
      l := (width div 2) - (w div 2) ;
      t := (height div 2) - (h div 2);
      r.Right := CellRect.Right-10;
      r.Left := r.Right-width;
      r.top := CellRect.top+t;
      r.Left := r.Left+l;
      r.Right := r.Left+w;
      r.Bottom := r.top+h;
      if Assigned(Shadow) then
      begin
        r2.Left := r.Left-10;
        r2.Right := r.Right+10;
        r2.Bottom  := r.Bottom+13;
        r2.Top := r.Bottom-8;
        TargetCanvas.StretchDraw(r2,Shadow);
      end;
      Avatar.Draw(TargetCanvas,r);
    end;
  end;
end;

end.
